# 获取 wbi 签名工具
# 参考链接：https://socialsisteryi.github.io/bilibili-API-collect/docs/misc/sign/wbi.html

import time
import urllib.parse
from functools import reduce
from hashlib import md5

import httpx

# fmt: off
# WBI签名所需的混淆密钥编码表，用于对img_key和sub_key进行字符顺序打乱
MIXIN_KEY_ENC_TAB = [
    46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
    33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
    61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
    36, 20, 34, 44, 52
]
# fmt: on


def get_mixin_key(orig: str) -> str:
    """
    对 img_key 和 sub_key 进行字符顺序打乱编码，生成混淆密钥

    参数:
        orig: img_key 和 sub_key 拼接后的字符串

    返回:
        经过编码处理后的32位混淆密钥字符串
    """
    # 使用reduce函数和MIXIN_KEY_ENC_TAB对原始字符串进行重排，取前32位
    return reduce(lambda s, i: s + orig[i], MIXIN_KEY_ENC_TAB, "")[:32]


def enc_wbi(params: dict[str, any], img_key: str, sub_key: str) -> dict[str, any]:
    """
    为请求参数进行WBI签名，这是B站API的一种安全验证机制

    参数:
        params: 需要签名的请求参数字典
        img_key: 从B站接口获取的图片密钥
        sub_key: 从B站接口获取的子密钥

    返回:
        包含签名信息的新参数字典
    """
    # 生成混淆密钥
    mixin_key = get_mixin_key(img_key + sub_key)
    # 获取当前时间戳(秒级)
    curr_time = round(time.time())
    # 添加时间戳参数
    params["wts"] = curr_time
    # 按照key对参数进行排序，这是签名计算的必要步骤
    params = dict(sorted(params.items()))
    # 过滤参数值中的特殊字符"!'()*"，这是B站API的要求
    params = {
        k: "".join(filter(lambda chr: chr not in "!'()*", str(v)))
        for k, v in params.items()
    }
    # 将参数转换为URL查询字符串格式
    query = urllib.parse.urlencode(params)
    # 计算签名值(w_rid)：将查询字符串与混淆密钥拼接后进行MD5哈希
    wbi_sign = md5((query + mixin_key).encode()).hexdigest()
    # 将签名添加到参数中
    params["w_rid"] = wbi_sign
    return params


async def get_wbi_keys() -> tuple[str, str]:
    """
    获取最新的img_key和sub_key，这两个密钥用于生成WBI签名
    这两个密钥会定期更新，因此需要动态获取

    返回:
        包含img_key和sub_key的元组
    """
    # 请求头信息，模拟浏览器行为
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
        "Referer": "https://www.bilibili.com/",
    }

    # 创建异步HTTP客户端并发送请求
    async with httpx.AsyncClient(headers=headers) as client:
        # 请求B站导航接口，该接口包含WBI所需的密钥信息
        resp = await client.get("https://api.bilibili.com/x/web-interface/nav")
        resp.raise_for_status()  # 如果响应状态码不是200，抛出异常
        json_content = resp.json()

    # 从响应中提取图片URL
    img_url: str = json_content["data"]["wbi_img"]["img_url"]
    sub_url: str = json_content["data"]["wbi_img"]["sub_url"]

    # 从URL中提取img_key和sub_key（去掉文件名后缀）
    img_key = img_url.rsplit("/", 1)[1].split(".")[0]
    sub_key = sub_url.rsplit("/", 1)[1].split(".")[0]
    return img_key, sub_key
